package com.googlecode.mapperdao

import com.googlecode.mapperdao.jdbc.Setup
import com.googlecode.mapperdao.jdbc.Jdbc

/**
 * @author kostantinos.kougios
 *
 *         22 May 2012
 */
object CommonEntities
{
	val AllEntities: List[EntityBase[_, _]] = List(
		ProductEntity,
		AttributeEntity,
		PersonEntity,
		CompanyEntity,
		OwnerEntity,
		HouseEntity,
		HusbandEntity,
		WifeEntity,
		ImageEntity,
		UniverseEntity,
		GalaxyEntity,
		StarEntity)

	/**
	 * A universe mapping. A universe consists of Galaxies which in turn have Stars which can be a Sun (Solar) or a BlackHole. A BlackHole can contain
	 * many-to-many universes (there goes my nobel price in physics!)
	 */
	def createUniverse(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle") {
			Setup.createSeq(jdbc, "UniverseSeq")
			Setup.createSeq(jdbc, "GalaxySeq")
			Setup.createSeq(jdbc, "StarSeq")
		}
		Setup.commonEntitiesQueries(jdbc).update("universe")
	}

	case class Universe(name: String, galaxies: Set[Galaxy])

	case class Galaxy(name: String, stars: Set[Star])

	trait Star
	{
		val name: String
	}

	case class Solar(name: String) extends Star

	case class BlackHole(name: String, universes: Set[Universe]) extends Star

	object UniverseEntity extends Entity[Int, SurrogateIntId, Universe]
	{
		val id = key("id") autogenerated (_.id)
		val name = column("name") to (_.name)
		val galaxies = onetomany(GalaxyEntity) to (_.galaxies)

		def constructor(implicit m: ValuesMap) = new Universe(name, galaxies) with Stored
		{
			val id: Int = UniverseEntity.id
		}
	}

	object GalaxyEntity extends Entity[Int, SurrogateIntId, Galaxy]
	{
		val id = key("id") autogenerated (_.id)
		val name = column("name") to (_.name)
		val stars = manytomany(StarEntity) to (_.stars)

		def constructor(implicit m: ValuesMap) = new Galaxy(name, stars) with Stored
		{
			val id: Int = GalaxyEntity.id
		}
	}

	object StarEntity extends Entity[Int, SurrogateIntId, Star]
	{
		val id = key("id") autogenerated (_.id)
		val name = column("name") to (_.name)
		val `type` = column("type") to {
			case s: Solar => 0
			case b: BlackHole => 1
		}
		val universes = manytomany(UniverseEntity) to {
			case s: Solar => Set()
			case b: BlackHole => b.universes
		}

		def constructor(implicit m: ValuesMap) = `type`.toInt match {
			case 0 =>
				new Solar(name) with Stored
				{
					val id: Int = StarEntity.id
				}
			case 1 =>
				new BlackHole(name, universes) with Stored
				{
					val id: Int = StarEntity.id
				}
		}
	}

	/**
	 * a product (i.e. something we buy from a shop) can have attributes (i.e. colour or brand)
	 */
	def createProductAttribute(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle") {
			Setup.createSeq(jdbc, "ProductSeq")
			Setup.createSeq(jdbc, "AttributeSeq")
		}
		Setup.commonEntitiesQueries(jdbc).update("product-attribute")
	}

	/**
	 * many to many
	 */
	case class Product(name: String, attributes: Set[Attribute])

	case class Attribute(name: String, value: String)

	object ProductEntity extends Entity[Int, SurrogateIntId, Product]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("ProductSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val attributes = manytomany(AttributeEntity) getter ("attributes") to (_.attributes)

		def constructor(implicit m: ValuesMap) = new Product(name, attributes) with Stored
		{
			val id: Int = ProductEntity.id
		}
	}

	object AttributeEntity extends Entity[Int, SurrogateIntId, Attribute]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("AttributeSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val value = column("value") to (_.value)
		val product = manytomanyreverse(ProductEntity) forQueryOnly() to (p => Nil)

		def constructor(implicit m: ValuesMap) = new Attribute(name, value) with Stored
		{
			val id: Int = AttributeEntity.id
		}
	}

	/**
	 * many to one
	 */
	def createPersonCompany(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle") {
			Setup.createSeq(jdbc, "PersonSeq")
			Setup.createSeq(jdbc, "CompanySeq")
		}
		Setup.commonEntitiesQueries(jdbc).update("person-company")
	}

	case class Person(name: String, company: Company)

	case class Company(name: String)

	object PersonEntity extends Entity[Int, SurrogateIntId, Person]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("PersonSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val company = manytoone(CompanyEntity) to (_.company)

		def constructor(implicit m: ValuesMap) = new Person(name, company) with Stored
		{
			val id: Int = PersonEntity.id
		}
	}

	object CompanyEntity extends Entity[Int, SurrogateIntId, Company]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("CompanySeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)

		def constructor(implicit m: ValuesMap) = new Company(name) with Stored
		{
			val id: Int = CompanyEntity.id
		}
	}

	/**
	 * one to many
	 */
	def createOwnerHouse(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle") {
			Setup.createSeq(jdbc, "HouseSeq")
			Setup.createSeq(jdbc, "OwnerSeq")
		}
		Setup.commonEntitiesQueries(jdbc).update("owner-house")
	}

	case class Owner(var name: String, owns: Set[House])

	case class House(address: String)

	object HouseEntity extends Entity[Int, SurrogateIntId, House]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("HouseSeq")
			case _ => None
		}) autogenerated (_.id)
		val address = column("address") to (_.address)

		def constructor(implicit m: ValuesMap) = new House(address) with Stored
		{
			val id: Int = HouseEntity.id
		}
	}

	object OwnerEntity extends Entity[Int, SurrogateIntId, Owner]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("OwnerSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val owns = onetomany(HouseEntity) to (_.owns)

		def constructor(implicit m: ValuesMap) = new Owner(name, owns) with Stored
		{
			val id: Int = OwnerEntity.id
		}
	}

	/**
	 * one to one
	 */
	def createHusbandWife(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle") {
			Setup.createSeq(jdbc, "WifeSeq")
		}
		Setup.commonEntitiesQueries(jdbc).update("husband-wife")
	}

	case class Husband(name: String, age: Int, wife: Wife)

	/**
	 * Note: the equality and hashCode implementations are "shallow"
	 * to avoid equality issues related to mock instances.
	 */
	case class Wife(name: String, age: Int, husband: Husband = null)
	{
		override def equals(o: Any) = o match {
			case w: Wife =>
				// skip the husband to avoid mock-related failures
				w.name == name && w.age == age
			case _ => false
		}

		override def hashCode = name.hashCode
	}

	object HusbandEntity extends Entity[Unit, NoId, Husband]
	{
		val name = column("name") to (_.name)
		val age = column("age") to (_.age)
		val wife = onetoone(WifeEntity) to (_.wife)

		declarePrimaryKey(wife)

		def constructor(implicit m: ValuesMap) = new Husband(name, age, wife) with Stored
	}

	object WifeEntity extends Entity[Int, SurrogateIntId, Wife]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("WifeSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val age = column("age") to (_.age)
		val husband = onetoonereverse(HusbandEntity) to (_.husband)

		def constructor(implicit m: ValuesMap) = new Wife(name, age, husband) with Stored
		{
			val id: Int = WifeEntity.id
		}
	}

	/**
	 * blob
	 */
	def createImage(jdbc: Jdbc) = {
		Setup.dropAllTables(jdbc)
		if (Setup.database == "oracle")
			Setup.createSeq(jdbc, "ImageSeq")

		Setup.commonEntitiesQueries(jdbc).update("image")
	}

	case class Image(name: String, data: Array[Byte])

	object ImageEntity extends Entity[Int, SurrogateIntId, Image]
	{
		val id = key("id") sequence (Setup.database match {
			case "oracle" => Some("ImageSeq")
			case _ => None
		}) autogenerated (_.id)
		val name = column("name") to (_.name)
		val data = column("data") to (_.data)

		def constructor(implicit m: ValuesMap) = new Image(name, data) with Stored
		{
			val id: Int = ImageEntity.id
		}
	}

}